package com.aldobucchi.core.os
{
	
	
	import com.adobe.utils.ArrayUtil;
	
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	import mx.core.*;
	import mx.events.StateChangeEvent;
	
	import com.aldobucchi.core.markers.ISingleton;
	import com.aldobucchi.core.os.thread.Thread;
	import com.aldobucchi.core.os.thread.ThreadState;
	import com.aldobucchi.utils.App;
	
	
	use namespace mx_internal;	
	
	/**
	 * 
	 * we are using "fixed priority scheduling" a la Java
	 * 
	 * 
	 * Multi-level preemptive dynamic priority scheduling.
	 * 
	 * - highest priority group is iterated in a round robin mode.
	 * 
	 * 
	 * The "main" thread is the real actionscript thread.
	 * The "outer" thread is a virtual ( conceptual ) thread that runs everything that is no in the queue
	 * A "thread" is represented by a thread object and is queued 
	 * 
	 * 
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */	
	public class ThreadScheduler implements ISingleton
	{
		
		
		private var threadSlice	:int = 100;
		private var totalSlice		:int = 300;
		private var waitSlice		:int = 400;
		
		
		
		private var threads:Array = [ ];
		
		
		private var lastRanThreads	:Dictionary = new Dictionary( );
		

		private var timer:Timer = new Timer( 100, 1 );
		



		private var time		:int;				// the last state change time
		private var running	:Boolean = false;	// indicates if we are running or waiting



		private var frames		:int = 0;
		


		public function ThreadScheduler( )
		{
			App.app.addEventListener( Event.ENTER_FRAME, app_enterFrame );
			timer.addEventListener( TimerEvent.TIMER_COMPLETE, timer_complete );	
		}
		
		

		
		private function app_enterFrame( event:Event ):void
		{
			if ( UIComponentGlobals.mx_internal::callLaterSuspendCount == 0 )
				tick( );
			else
				trace( "suspend" );
		}
		

		
		// TODO: make internal
		public function queue( thread:Thread ):void
		{
			thread.addEventListener( StateChangeEvent.CURRENT_STATE_CHANGE, thread_currentStateChange, false, 0, true );
			threads.push( thread );
		}
		
		
		
		
		private function thread_currentStateChange( event:StateChangeEvent ):void
		{
			
			var thread:Thread = event.target as Thread;
			
			if ( event.newState == ThreadState.TERMINATED )
			{
				// remove from the queue
				ArrayUtil.removeValueFromArray( threads, thread );				
			}
		}		
		
		
		
		
		
		
		
		private function timer_complete( event:TimerEvent ):void
		{
		
		
		}
		
		
		
		
		
		
//		private function startRunning( ):void
//		{
//			var t0:int = getTimer( );
//			// start consuming the total slice
//			while ( true )
//			{
//				
//							
//			
//			}
//		}
//		
//		
//		private function startWaiting( ):void
//		{
//		
//		
//		}
//		
//		
//		
//		
//		
//		
//		
//		
//		
//		
//		private function runThread( thread:Thread, maxTime:int ):int
//		{
//			var t0:int=getTimer( ), elapsed;
//			while ( true )
//			{
//				if ( thread.currentState == ThreadState.RUNNING )
//				{
//				
//				}
//			}		
//		}
//		
//		
//		
		
		
		
		
		
		
		private function tick( ):void
		{
			
			
			if ( frames < 3 )
			{
				frames++;
				return;
			}
			frames = 0;
			
			
			var t0:int = getTimer( );		
			var elapsed:int;
			
			while ( true )
			{
			
				step( );
				elapsed = getTimer( ) - t0;
			
				if ( elapsed > 300 )
					return;

			}
		
		}
		
		
		
		
		
		private function step( ):Boolean
		{

			// get the next available thread and run a step on it
			var hp:Array = getThreadsWithHighestPriority( true );
			
			if ( hp.length == 0 )
				return false;
			
			var thread:Thread;
			
			for each ( var t:Thread in hp )
			{
				if ( lastRanThreads[ t ] != true )
				{
					if ( t.currentState != ThreadState.BLOCKED )
					{
						thread = t;			
						break;
					}
				}
			}
			
			
			if ( thread == null )
			{
				lastRanThreads = new Dictionary( );
				thread = hp[0] as Thread;
			}
			
			
			lastRanThreads[ thread ] = true;			
						
			thread.step( ); // for now only one step
			return true;

		}
		
		




		private function getThreadsWithHighestPriority( excludeBlocked:Boolean = false ):Array
		{
			var t:Array = getThreadsByPriority( excludeBlocked );
			for ( var i:int=Thread.MAX_PRIORITY ; i>=Thread.MIN_PRIORITY; i-- )
				if ( t[i] )
					return t[i];											
			
			return [];
		}





		/**
		 * 
		 * This is being computed on every pass. It could be
		 * done the other way... by listening to thread events
		 * 
		 * @return 
		 * 
		 */
		private function getThreadsByPriority( excludeBlocked:Boolean = false ):Array
		{
			var byPriority:Array = [ ];
			for each ( var thread:Thread in threads )
			{
				
				if ( excludeBlocked && ( thread.currentState == ThreadState.BLOCKED ) )
				{
					continue;
				}
				
				if ( thread.priority == 0 ) // zero priority is PAUSE
				{
					continue;
				}
				
				
				
				if ( ! ( byPriority[ thread.priority ] is Array ) )
					byPriority[ thread.priority ] = [];
				 ( byPriority[ thread.priority ] as Array ).push( thread );		
			}
			
			return byPriority;
		}




		/**
		 * 
		 * Returns the current highest priority found in the queue
		 * 
		 * @return 
		 * 
		 */
		private function getHighestPriority( ):int
		{
			var highest:int=-1;
			for each ( var thread:Thread in threads )
				if ( thread.priority > highest )
					highest = thread.priority;
			return highest;
		}









		
		
		
		
		
		
		private static var _i:ThreadScheduler;
		
		public static function get i( ):ThreadScheduler
		{
			if ( _i == null )
				_i = new ThreadScheduler( );
			return _i;
		}
		
		
		
		
		


	}

}